//
//  PLEndpoint.m
//  Plankton
//
//  Created by Marc Schlichte on 2008-05-03.
//  Copyright 2008 Framework Labs. All rights reserved.
//

#import "PLEndpoint.h"

#import <netinet/in.h>


@implementation PLEndpoint

@synthesize port, delegate;

- (void)_notifyProblem:(NSString *)problem
{
    if ([delegate respondsToSelector:@selector(endpoint:encounteredProblem:)]) {
        [delegate endpoint:self encounteredProblem:problem];
    }
}

static void HandleSocketData(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
{
    PLEndpoint *endpoint = (PLEndpoint *)info;
    id delegate = endpoint.delegate;
    if ([delegate respondsToSelector:@selector(endpoint:didReceiveData:from:)]) {
        [delegate endpoint:endpoint didReceiveData:(NSData *)data from:(NSData *)address];
    }
}

- (id)init
{
    if (self = [super init]) {
        
        // create a dgram socket
        CFSocketContext ctx;
        ctx.version = 0;
        ctx.info = self;
        ctx.retain = NULL;
        ctx.release =  NULL;
        ctx.copyDescription = NULL;
        sock = CFSocketCreate(NULL, PF_INET, SOCK_DGRAM, 0, kCFSocketDataCallBack, HandleSocketData, &ctx);
        if (!sock) {
            [self _notifyProblem:@"can't create socket"];
            [self release];
            return nil;
        }
        
        // assign own address to socket - specifying 0 for arbitrary port
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(0);
        saddr.sin_addr.s_addr = INADDR_ANY;
        CFDataRef daddr = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)&saddr, sizeof(saddr), kCFAllocatorNull);
        CFSocketError err = CFSocketSetAddress(sock, daddr);
        CFRelease(daddr);
        if (err) {
            [self _notifyProblem:@"can't set address"];
            [self release];
            return nil;
        }
        
        // extract real port
        daddr = CFSocketCopyAddress(sock);
        struct sockaddr_in *saddrp = (struct sockaddr_in *)CFDataGetBytePtr(daddr);
        port = ntohs(saddrp->sin_port);
        CFRelease(daddr);
        
        // create runloop source and add to current run-loop
        CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(NULL, sock, 0);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        CFRelease(source);
    }
    return self;
}

- (void)dealloc
{
    if (sock) CFRelease(sock);
    [super dealloc];
}

- (NSString *)hostname
{
    char buf[256];
    if (gethostname(buf, 255)) {
        [self _notifyProblem:@"gethostname failed"];
        return @"locahost";
    }
    return [NSString stringWithFormat:@"%s.", buf];
}

static in_addr_t GetIPForHost(NSString *hostname) 
{
    CFHostRef host = CFHostCreateWithName(NULL, (CFStringRef)hostname);
    if (!CFHostStartInfoResolution(host, kCFHostAddresses, NULL)) {
        CFRelease(host);
        NSLog(@"can't resolve address for host:%@", hostname);
        return 0;
    }
    CFArrayRef addrs = CFHostGetAddressing(host, NULL);
    CFIndex count = CFArrayGetCount(addrs);
    in_addr_t addr = 0;
    CFIndex i;
    for (i = 0; i < count; ++i) {
        CFDataRef daddr = (CFDataRef)CFArrayGetValueAtIndex(addrs, i);
        struct sockaddr_in *saddr = (struct sockaddr_in *)CFDataGetBytePtr(daddr);
        addr = saddr->sin_addr.s_addr;
        if (addr) {
            break;
        }
    }
    CFRelease(host);
    
    return addr;
}

- (void)sendData:(NSData *)data toHost:(NSString *)host port:(unsigned short)toPort;
{
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(toPort);
    saddr.sin_addr.s_addr = GetIPForHost(host);
    CFDataRef daddr = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)&saddr, sizeof(saddr), kCFAllocatorNull);
    
    CFSocketError err = CFSocketSendData(sock, daddr, (CFDataRef)data, 0);
    CFRelease(daddr);
    
    if (err) {
        [self _notifyProblem:@"can't send data"];
    }
}

@end

